//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-06-01     ZhangYihua   first version
//
// Description  : 
//################################################################################

module fifo_status #(
parameter           DEPTH                   = 32,       // must DEPTH>=2

// the following parameters are calculated automatically
parameter           ADDR_BW                 = $clog2(DEPTH),    // address range [0:DEPTH-1]
parameter           CNT_BW                  = $clog2(DEPTH+1)   // counter range [0:DEPTH]
) ( 
input                                       rst_n,
input                                       clk,

input                                       wen,            // waddr inc when wen==1'b1
input                                       ren,            // raddr inc when ren==1'b1
input               [CNT_BW-1:0]            cnt,

output  reg                                 full,
output  reg                                 afull,
output  reg                                 empty,
output  reg                                 aempty,

input               [ADDR_BW-1:0]           cfg_afull_th,   // afull_th<cnt<=DEPTH is afull
input               [ADDR_BW-1:0]           cfg_aempty_th   // 0<=cnt<aempty_th   is aempty
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          DEPTH_E                 = 1<<ADDR_BW;
localparam  [CNT_BW-1:0]    DEPTH_V         = DEPTH;
localparam  [CNT_BW-1:0]    DEPTH_DEC       = DEPTH - 1;
localparam  [CNT_BW-1:0]    ONE             = 1;
localparam  [CNT_BW-1:0]    ZERO            = 0;

wire                                        cnt_neq_zero;

wire                                        wen_only;
wire                                        ren_only;
wire                [CNT_BW-1:0]            afull_th;
wire                [CNT_BW-1:0]            afull_th_inc;
wire                [CNT_BW-1:0]            aempty_th;
wire                [CNT_BW-1:0]            aempty_th_dec;

//################################################################################
// main
//################################################################################

assign wen_only = wen & (~ren);
assign ren_only = ren & (~wen);

assign cnt_neq_zero = (cnt!=ZERO) ? 1'b1 : 1'b0;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        empty <=`U_DLY 1'b1;
    end else begin
        if ((ren_only==1'b1) && (cnt==ONE))
            empty <=`U_DLY 1'b1;
        else if (((wen==1'b1) || (cnt_neq_zero==1'b1)) && (empty==1'b1))
            empty <=`U_DLY 1'b0;
        else if ((cnt_neq_zero==1'b0) && (empty==1'b0))
            empty <=`U_DLY 1'b1;
        else
            ;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        full <=`U_DLY 1'b0;
    end else begin
        if ((wen_only==1'b1) && (cnt==DEPTH_DEC))
            full <=`U_DLY 1'b1;
        else if (((ren==1'b1) || (cnt<DEPTH_V)) && (full==1'b1))
            full <=`U_DLY 1'b0;
        else if ((cnt>=DEPTH_V) && (full==1'b0))
            full <=`U_DLY 1'b1;
        else
            ;
    end
end

generate if (DEPTH==DEPTH_E) begin:G_PWR2   // DEPTH == 2^ADDR_BW

    assign afull_th  = {1'b0, cfg_afull_th};
    assign aempty_th = {1'b0, cfg_aempty_th};

end else begin:G_NPWR2   // DEPTH < 2^ADDR_BW

    assign afull_th  = cfg_afull_th;
    assign aempty_th = cfg_aempty_th;

end endgenerate

assign afull_th_inc  = afull_th  + 1'd1;
assign aempty_th_dec = aempty_th - 1'd1;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        afull <=`U_DLY 1'b0;
    end else begin
        if ((wen_only==1'b1) && (cnt==afull_th))             // fifo_cnt!=DEPTH
            afull <=`U_DLY 1'b1;
        else if ((ren_only==1'b1) && (cnt==afull_th_inc))    // fifo_cnt!=0
            afull <=`U_DLY 1'b0;
        else if (cnt>afull_th)
            afull <=`U_DLY 1'b1;
        else
            afull <=`U_DLY 1'b0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        aempty <=`U_DLY 1'b1;
    end else begin
        if ((ren_only==1'b1) && (cnt==aempty_th))    // fifo_cnt!=0
            aempty <=`U_DLY 1'b1;
        else if ((wen_only==1'b1) && (cnt==aempty_th_dec))   // fifo_cnt!=DEPTH
            aempty <=`U_DLY 1'b0;
        else if (cnt<aempty_th) 
            aempty <=`U_DLY 1'b1;
        else
            aempty <=`U_DLY 1'b0;
    end
end

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

reg     OVERFLOW_CHKEN;
reg     UNDERFLOW_CHKEN;

a_overflow: assert property (@(posedge clk) disable iff (!rst_n)
    ((OVERFLOW_CHKEN!==1'b0)&wen |-> (~full))
) else begin
    $error("fifo is overflow.");
end

a_underflow: assert property (@(posedge clk) disable iff (!rst_n)
    ((UNDERFLOW_CHKEN!==1'b0)&ren |-> (~empty))
) else begin
    $error("fifo is underflow.");
end

// synopsys translate_on
`endif

endmodule
